// Copyright (c) 2019 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation

/// Parses `swiftc` compiler times generated by
/// `-Xfrontend` flags such as `-debug-time-function-bodies` and `-debug-time-expression-type-checking`
public class SwiftCompilerParser {

    /// Array to store the text of the log sections and their commandDetailDesc
    var commands = [(String, String)]()

    /// Dictionary to store the function times found per filepath
    var functionsPerFile: [String: [SwiftFunctionTime]]?

    /// Dictionary to store the type checker times found per filepath
    var typeChecksPerFile: [String: [SwiftTypeCheck]]?

    let functionTimes = SwiftCompilerFunctionTimeOptionParser()

    let typeCheckTimes = SwiftCompilerTypeCheckOptionParser()

    public func addLogSection(_ logSection: IDEActivityLogSection) {
        commands.append((logSection.text, logSection.commandDetailDesc))
    }

    /// Checks the `commandDetailDesc` stored by the function `addLogSection`
    /// to know if the command `text` contains data about the swift function times.
    /// If there is data, the `text` wit that raw data is returned as part of a Set.
    ///
    /// - Returns: a Dictionary of Strings with the raw Swift compiler times data as key and
    /// the number of ocurrences as value
    public func findRawSwiftTimes() -> [String: Int] {
        let insertQueue = DispatchQueue(label: "swift_function_times_queue")
        var textsAndOccurrences: [String: Int] = [:]
        DispatchQueue.concurrentPerform(iterations: commands.count) { index in
            let (rawFunctionTimes, commandDesc) = commands[index]

            let hasCompilerFlag = functionTimes.hasCompilerFlag(commandDesc: commandDesc)
                                  || typeCheckTimes.hasCompilerFlag(commandDesc: commandDesc)

            guard hasCompilerFlag && rawFunctionTimes.isEmpty == false else {
                return
            }
            insertQueue.sync {
                let outputOccurrence = (textsAndOccurrences[rawFunctionTimes] ?? 0) + 1
                textsAndOccurrences[rawFunctionTimes] = outputOccurrence
            }
        }
        return textsAndOccurrences
    }

    /// Parses the swift function times and store them internally
    public func parse() {
        let rawTexts = findRawSwiftTimes()
        functionsPerFile = functionTimes.parse(from: rawTexts)
        typeChecksPerFile = typeCheckTimes.parse(from: rawTexts)
    }

    public func findFunctionTimesForFilePath(_ filePath: String) -> [SwiftFunctionTime]? {
        // File paths found in IDEActivityLogSection.text are unescaped
        // so percent encoding needs to be removed from filePath
        guard let unescapedFilePath = filePath.removingPercentEncoding else {
            return nil
        }
        return functionsPerFile?[unescapedFilePath]
    }

    public func findTypeChecksForFilePath(_ filePath: String) -> [SwiftTypeCheck]? {
        // File paths found in IDEActivityLogSection.text are unescaped
         // so percent encoding needs to be removed from filePath
        guard let unescapedFilePath = filePath.removingPercentEncoding else {
            return nil
        }
        return typeChecksPerFile?[unescapedFilePath]
    }

    public func hasFunctionTimes() -> Bool {
        guard let functionsPerFile = functionsPerFile else {
            return false
        }
        return functionsPerFile.isEmpty == false
    }

    public func hasTypeChecks() -> Bool {
        guard let typeChecksPerFile = typeChecksPerFile else {
            return false
        }
        return typeChecksPerFile.isEmpty == false
    }

}
